package com.openroom.db;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.log4j.Logger;

import com.openroom.model.BackupMaster;
import com.openroom.model.Catalog;
import com.openroom.model.Folder;
import com.openroom.model.ModelLocator;
import com.openroom.model.Photograph;

public class DBHelper {
    private static final Logger LOGGER = Logger.getLogger(DBHelper.class);
    private static DBHelper instance = new DBHelper();
    private SqlSessionFactory sqlMapper;
    private SqlSession currentSession = null;

    int numOfUncommitedCalls = 0;

    private static int MAX_NUM_OF_UNCOMMITTED_CALLS = 15;

    private DBHelper() {
        String resource = "Configuration.xml";
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        sqlMapper = new SqlSessionFactoryBuilder().build(reader);
    }

    public static DBHelper getInstance() {
        return instance;
    }

    public synchronized List<Folder> getallImportedFolders() {
        try {
            return getSession().selectList("selectAllImportedFolder");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private SqlSession getSession() {
        if (currentSession == null) {
            currentSession = sqlMapper.openSession();
        }
        return currentSession;
    }

    public synchronized List<Photograph> getAllPhotographsOfFolder(Long folderId) {
        long start = System.nanoTime();
        List<Photograph> allPhotographsOfFolder = getAllPhotographsOfFolder(
                folderId, false);
        LOGGER.info(String.format("Got %d images for folder id: %d",
                allPhotographsOfFolder.size(), folderId));
        long end = System.nanoTime();
        LOGGER.debug("DBHelper.getAllPhotographsOfFolder() finished in "
                + (end - start) / 1000 + " ms");
        return allPhotographsOfFolder;
    }

    public synchronized List<Photograph> getAllPhotographsOfFolder(Long folderId,
            boolean includeRejected) {
        try {
            if (includeRejected)
                return getSession().selectList(
                        "selectAllImagesForFolderIncludeRejected", folderId);
            else
                return getSession().selectList("selectAllImagesForFolder",
                        folderId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public synchronized void deleteFolder(Long folderId) {
        try {
            getSession().delete("deleteAssociationFromCatalog", folderId);
            getSession().delete("deleteAllImagesFromFolder", folderId);
            getSession().delete("deleteFolder", folderId);
            commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public synchronized void deleteImage(Long imageId) {
        try {
            getSession().delete("deleteImageAssociationFromCatalog", imageId);
            getSession().delete("deleteImage", imageId);
            commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public synchronized List<Photograph> selectAllImagesForFolderWithRating(Long folderId,
            int rating) {
        try {
            HashMap<String, Object> parameter = new HashMap<String, Object>();
            parameter.put("folderId", folderId);
            parameter.put("ratingValue", rating);
            getSession().update("updateRatingForFolder",parameter);
            commit();
            return getSession().selectList(
                    "selectAllImagesForFolderWithRating", parameter);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public synchronized List<Photograph> getAllPhotographsOfCatalog(Long catalogId) {
        try {
            return getSession().selectList("selectAllImagesForCatalog",
                    catalogId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public synchronized Photograph selectImage(Long photoId) {
        try {
            return getSession().selectOne("selectImage", photoId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public synchronized void changeRating(Long photoId, int rating) {
        SqlSession session = getSession();
        try {
            HashMap<String, Object> parameter = new HashMap<String, Object>();
            parameter.put("ratingValue", rating);
            parameter.put("flagValue", -1);
            parameter.put("photoId", photoId);
            session.update("changeRating", parameter);
            session.commit();
        } catch (Exception e) {
            e.printStackTrace();
            session.rollback();
        }
    }

    public synchronized void savePhotographs(Photograph photograph) {
        SqlSession session = getSession();
        photograph = convertFromAbsoluteToRelativePath(photograph);
        try {
            session.insert("addPhotograph", photograph);
            commitIfNeeded();
            // session.commit();
        } catch (Exception e) {
            e.printStackTrace();
            session.rollback();
        }
    }

    private synchronized Photograph convertFromAbsoluteToRelativePath(Photograph photograph) {
        photograph.setRawFilePath(getRelativePath(photograph.getRawFilePath()));
        photograph.setThumbnailPath(getRelativePath(photograph
                .getThumbnailPath()));
        if (photograph.getPreviewImagePath() != null)
            photograph.setPreviewImagePath(getRelativePath(photograph
                    .getPreviewImagePath()));
        return photograph;
    }

    private String getRelativePath(String rawFilePath) {
        try {
            int indexOf = rawFilePath.indexOf("/./");
            if (indexOf == -1)
                return rawFilePath;
            String subSequence = rawFilePath.substring(indexOf + 1,
                    rawFilePath.length());
            return subSequence;
        } catch (Exception e) {
            System.err.println(rawFilePath);
            e.printStackTrace();
        }
        return rawFilePath;
    }

    private void commitIfNeeded() {
        numOfUncommitedCalls++;
        if (numOfUncommitedCalls >= MAX_NUM_OF_UNCOMMITTED_CALLS)
            getSession().commit();
    }

    public synchronized void commit() {
        getSession().commit();
    }

    public synchronized Long getLastInsertedValue() {
        return getSession().selectOne("getLastInsertValue");
    }

    public synchronized void rejectImage(Long imageId) {
        SqlSession session = getSession();
        try {
            HashMap<String, Object> parameter = new HashMap<String, Object>();
            parameter.put("photoId", imageId);
            parameter.put("rejected", Boolean.TRUE);
            session.update("rejectImage", parameter);
            commitIfNeeded();
        } catch (Exception e) {
            e.printStackTrace();
            session.rollback();
        }
    }

    public synchronized void updatePreviewPath(Long imageId, String previewPath) {
        Photograph image = selectImage(imageId);
        updatePreviewPath(imageId, previewPath, image.getRawFilePath(),
                image.getThumbnailPath(), null);
    }

    public synchronized void updatePreviewPath(Long imageId, String previewPath,
            String rawFilePath, String tnailPath, Long masterId) {
        SqlSession session = getSession();
        try {
            HashMap<String, Object> parameter = new HashMap<String, Object>();
            parameter.put("photoId", imageId);
            parameter.put("previewPath", getRelativePath(previewPath));
            parameter.put("rawFilePath", getRelativePath(rawFilePath));
            parameter.put("tnailPath", getRelativePath(tnailPath));
            parameter.put("masterId", masterId);
            int n = session.update("updatePreviewPath", parameter);
            commit();
        } catch (Exception e) {
            e.printStackTrace();
            session.rollback();
        }
    }

    public synchronized List<Catalog> getAllCatalogs() {
        try {
            List<Catalog> selectList = getSession().selectList("getAllCatalog");
            return selectList;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public synchronized void unRejectImage(Long imageId) {
        SqlSession session = getSession();
        try {
            HashMap<String, Object> parameter = new HashMap<String, Object>();
            parameter.put("photoId", imageId);
            parameter.put("rejected", Boolean.FALSE);
            session.update("rejectImage", parameter);
            commitIfNeeded();
        } catch (Exception e) {
            e.printStackTrace();
            session.rollback();
        }
    }

    public synchronized void updateImage(Photograph photograph) {
        SqlSession session = getSession();
        try {
            session.update("rejectSelectImage", photograph);
            commit();
        } catch (Exception e) {
            e.printStackTrace();
            session.rollback();
        }
    }

    public synchronized Catalog getOrCreateCatalog(String dateString) {
        try {
            SimpleDateFormat format = new SimpleDateFormat(
                    "yyyy:MM:dd HH:mm:ss");
            Date parse = format.parse(dateString);
            return getOrCreateCatalog(parse);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public synchronized Catalog getOrCreateCatalog(Date date) {
        try {
            Catalog catalog = new Catalog(date);
            Object selectOne = getSession().selectOne("getCatalog", catalog);
            if (selectOne == null) {
                LOGGER.info("Creating new catalog" + date);
                getSession().insert("insertCatalog", catalog);
                commitIfNeeded();
                ModelLocator.getInstance().getAllCatalogs().add(catalog);
                return catalog;
            } else {
                LOGGER.debug("using old catalog" + date);
                return (Catalog) selectOne;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public synchronized Folder getOrCreateFolder(String localPath) {
        try {
            Folder folder = new Folder(localPath);
            Object selectOne = getFolderByPath(localPath);
            if (selectOne == null) {
                getSession().insert("addFolder", folder);
                commitIfNeeded();
                return folder;
            } else {
                return (Folder) selectOne;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public synchronized Folder getFolderByPath(String localPath) {
        Folder folder = new Folder(localPath);
        Folder selectOne = getSession().selectOne("getFolder", folder);
        return selectOne;
    }

    public synchronized void saveCatalogAssociation(Catalog catalog, Photograph photograph) {
        try {
            HashMap<String, Integer> parameter = new HashMap<String, Integer>();
            parameter.put("catalogId", new Integer(catalog.getCatalogId()
                    .intValue()));
            parameter.put("photoId", new Integer(photograph.getPhotoId()
                    .intValue()));
            getSession().insert("insertCatalogImageAssociation", parameter);
            commitIfNeeded();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public synchronized void executeWithConnection(DBTemplate template) {
        Connection connection = null;
        SqlSession openSession = getSession();
        try {
            connection = openSession.getConnection();
            template.execute(connection);
            commitIfNeeded();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                openSession.rollback();
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    }

    public synchronized List<Photograph> selectAllImagesForCatalogWithRating(Long catalogId,
            Integer rating) {
        try {
            HashMap<String, Object> parameter = new HashMap<String, Object>();
            parameter.put("catalogId", catalogId);
            parameter.put("ratingValue", rating);
            getSession().update("updateRatingForCatalog",parameter);
            commit();
            return getSession().selectList(
                    "selectAllImagesForCatalogWithRating", parameter);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public synchronized Long creatBackupMaster(Long photoId) {
        Photograph selectImage = selectImage(photoId);
        BackupMaster backupMaster = new BackupMaster(selectImage);
        getSession().insert("createBackupMaster", backupMaster);
        commit();
        return backupMaster.getBackupId();
    }

    public synchronized Photograph restoreOriginal(Long photoId) {
        Photograph selectImage = selectImage(photoId);
        BackupMaster backup = getBackup(selectImage);
        if (backup == null) {
            return selectImage;
        }
        LOGGER.info("master" + backup.getRawFilePath());
        updatePreviewPath(photoId, backup.getPreviewImagePath(),
                backup.getRawFilePath(), backup.getThumbnailPath(), null);
//        FileUtils.deleteQuietly(new File(selectImage.getPreviewImagePath()));
//        FileUtils.deleteQuietly(new File(selectImage.getThumbnailPath()));
//        FileUtils.deleteQuietly(new File(selectImage.getRawFilePath()));
        return selectImage(photoId);
    }

    public synchronized BackupMaster getBackup(Photograph selectImage) {
        BackupMaster backup = getSession().selectOne("retrieveMaster",
                selectImage.getMasterId());
        return backup;
    }
}
